Show DR for verified partner websites - #4024
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds Ahrefs domain-rating collection and website cron batch processing with Qstash continuation; refactors YouTube cron for cursor-based pagination and richer change detection; surfaces domain rating as "N DR" in partner platform UI and forms; refactors partner card UI and tweaks button styling. ChangesPartner Platforms Metrics Collection
Sequence DiagramsequenceDiagram
participant Cron as Cron/Qstash
participant Route as Website Cron Route
participant Prisma as Prisma DB
participant Ahrefs as Ahrefs API
participant Qstash as Qstash
Cron->>Route: POST /api/cron/partner-platforms/website (startingAfter)
Route->>Prisma: Query verified website platforms (batch, cursor-paginated)
Prisma-->>Route: Website partner records
loop For each website record
Route->>Ahrefs: GET domain-rating-free (normalized domain)
Ahrefs-->>Route: JSON with domain_rating
Route->>Prisma: Update subscribers & lastCheckedAt (if changed)
end
alt Batch full (size === BATCH_SIZE)
Route->>Qstash: publishJSON next batch (startingAfter = last id)
Qstash-->>Route: scheduled
else Final partial batch
Route-->>Route: log completion
end
Route-->>Cron: 200 OK
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/(ee)/api/cron/partner-platforms/youtube/route.ts (1)
91-98:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
find()only refreshes the first row for a shared channel ID.Line 92 uses
find(), so one YouTube payload only updates the firstPartnerPlatformwhoseplatformIdmatcheschannel.id. The Prisma schema shown forPartnerPlatformdoes not makeplatformIdunique, so any additional verified rows pointing at the same channel will stay stale. Pre-group byplatformIdor usefilter()here and apply the same update to every match.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/cron/partner-platforms/youtube/route.ts around lines 91 - 98, The current updateChunk.map callback uses channelChunk.find(...) which only returns the first PartnerPlatform with a matching platformId, so duplicate PartnerPlatform rows with the same platformId remain stale; change the logic inside the updateChunk.map callback to collect all matches (e.g., use channelChunk.filter(p => p.platformId === channel.id) or pre-group channelChunk by platformId) and then apply the same update to every matched PartnerPlatform instead of a single partnerPlatform — update any code paths referencing partnerPlatform (the variable from the find call) to iterate over the matches and run the existing update logic per item.
🧹 Nitpick comments (1)
apps/web/lib/partners/partner-platforms.ts (1)
35-40: ⚡ Quick winConsider extracting domain rating formatting into a shared helper.
The domain rating formatting logic
${Number(website.subscribers)} DRis duplicated inapps/web/ui/partners/partner-platforms-form.tsx(lines 688-698). To maintain consistency and reduce the risk of divergence, consider extracting this into a shared helper function.♻️ Suggested refactor
Create a helper function in this file:
function formatDomainRating(subscribers: bigint | null): string | null { const domainRating = subscribers ?? 0n; return domainRating > 0n ? `${Number(domainRating)} DR` : null; }Then use it in both locations:
info: [ - website?.subscribers && website?.verifiedAt - ? `${Number(website.subscribers)} DR` - : null, + website?.verifiedAt ? formatDomainRating(website.subscribers) : null, ].filter(Boolean),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/partners/partner-platforms.ts` around lines 35 - 40, Extract the duplicated domain rating formatting into a shared helper named formatDomainRating(subscribers) that returns a string like "N DR" or null; replace the inline expression in partner-platforms.ts (the info array entry that uses website?.subscribers) with a call to formatDomainRating(website?.subscribers) and update partner-platforms-form.tsx to call the same helper instead of repeating `${Number(...)} DR`; ensure the helper treats bigint|null safely (defaults to 0n) and returns null for zero or missing values so both components keep identical behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/web/app/`(ee)/api/cron/partner-platforms/youtube/route.ts:
- Around line 91-98: The current updateChunk.map callback uses
channelChunk.find(...) which only returns the first PartnerPlatform with a
matching platformId, so duplicate PartnerPlatform rows with the same platformId
remain stale; change the logic inside the updateChunk.map callback to collect
all matches (e.g., use channelChunk.filter(p => p.platformId === channel.id) or
pre-group channelChunk by platformId) and then apply the same update to every
matched PartnerPlatform instead of a single partnerPlatform — update any code
paths referencing partnerPlatform (the variable from the find call) to iterate
over the matches and run the existing update logic per item.
---
Nitpick comments:
In `@apps/web/lib/partners/partner-platforms.ts`:
- Around line 35-40: Extract the duplicated domain rating formatting into a
shared helper named formatDomainRating(subscribers) that returns a string like
"N DR" or null; replace the inline expression in partner-platforms.ts (the info
array entry that uses website?.subscribers) with a call to
formatDomainRating(website?.subscribers) and update partner-platforms-form.tsx
to call the same helper instead of repeating `${Number(...)} DR`; ensure the
helper treats bigint|null safely (defaults to 0n) and returns null for zero or
missing values so both components keep identical behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 960416a3-e8f9-4fd4-a5de-f2e14053bf14
📒 Files selected for processing (6)
apps/web/app/(ee)/api/cron/partner-platforms/route.tsapps/web/app/(ee)/api/cron/partner-platforms/website/get-domain-rating.tsapps/web/app/(ee)/api/cron/partner-platforms/website/route.tsapps/web/app/(ee)/api/cron/partner-platforms/youtube/route.tsapps/web/lib/partners/partner-platforms.tsapps/web/ui/partners/partner-platforms-form.tsx
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/network/network-partner-card.tsx:
- Around line 257-259: The current logic uses remainingInvites === 0 but
remainingInvites can be undefined while loading; change the logic in the
component that calls usePartnerNetworkInvitesUsage so that undefined is treated
as "loading" and prevents invites: compute an isLoading flag (e.g.,
remainingInvites === undefined), set atNetworkInviteLimit only when
remainingInvites !== undefined && remainingInvites === 0, and derive disabled as
isLoading || (atNetworkInviteLimit && !trialActive) so invites are blocked until
remainingInvites is known.
- Around line 355-368: As currently written As is set to "a" or "div" but the
element always receives anchor-only attributes (href, target, rel), which causes
invalid attributes on a div; update the render so that href/target/rel are only
applied when href is truthy (e.g., compute anchorProps when href exists and
spread them into the element) while still rendering As (the variable) and
keeping onClick={(e) => e.stopPropagation()} and content; reference the As
variable and the surrounding JSX (DynamicTooltipWrapper and the element that
receives href/target/rel) to locate where to conditionally apply those props.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f736e2b9-7c20-48fb-a7b9-b610dd2b4e7a
📒 Files selected for processing (3)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/network-partner-card.tsxapps/web/lib/partners/partner-platforms.tsapps/web/ui/partners/partner-star-button.tsx
✅ Files skipped from review due to trivial changes (1)
- apps/web/ui/partners/partner-star-button.tsx
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/network-partner-card.tsx (1)
261-263:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
remainingInvitesmay beundefinedduring initial load.Per context snippet 1,
usePartnerNetworkInvitesUsagereturnsremainingasnumberbut it can beundefinedwhile loading. The comparisonremainingInvites === 0evaluates tofalsewhenundefined, allowing invites before the limit is known.🛡️ Proposed fix to handle undefined state
const { remaining: remainingInvites } = usePartnerNetworkInvitesUsage(); - const atNetworkInviteLimit = remainingInvites === 0; - const disabled = atNetworkInviteLimit && !trialActive; + const isLoadingInvites = remainingInvites === undefined; + const atNetworkInviteLimit = remainingInvites !== undefined && remainingInvites === 0; + const disabled = isLoadingInvites || (atNetworkInviteLimit && !trialActive);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/network/network-partner-card.tsx around lines 261 - 263, remainingInvites can be undefined during load so change the boolean logic to explicitly check for undefined: compute atNetworkInviteLimit as remainingInvites !== undefined && remainingInvites === 0 (using the value from usePartnerNetworkInvitesUsage), and ensure disabled uses that guarded atNetworkInviteLimit together with trialActive so we don't erroneously allow invites while remainingInvites is still loading.
🧹 Nitpick comments (3)
apps/web/app/(ee)/api/cron/partner-platforms/youtube/route.ts (1)
89-141: ⚡ Quick winIndividual update failures will abort the entire batch.
If any
prisma.partnerPlatform.updatethrows (e.g., network error, constraint violation),Promise.allrejects immediately, causing the remaining channels in subsequent chunks to be skipped. Since this is a daily cron with potentially thousands of records, consider wrapping individual updates in try-catch to isolate failures and continue processing.♻️ Suggested per-record error isolation
await Promise.all( updateChunk.map(async (channel) => { + try { const partnerPlatform = channelChunk.find( (p) => p.platformId === channel.id, ); if (!partnerPlatform) { return; } // ... existing logic ... console.log( `Updated YouTube stats for @${partnerPlatform.identifier}`, newStats, ); + } catch (error) { + console.error( + `Failed to update YouTube stats for channel ${channel.id}:`, + error, + ); + } }), );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/cron/partner-platforms/youtube/route.ts around lines 89 - 141, The batch uses Promise.all over updateChunk.map so a single failed prisma.partnerPlatform.update will reject the whole chunk and abort remaining work; modify the anonymous async map callback (the function iterating updateChunks / processing channel and partnerPlatform) to catch errors per-record—wrap the logic around the prisma.partnerPlatform.update (and any awaiting work) in a try-catch (or switch to Promise.allSettled) and on error log the channel/partnerPlatform id and continue so one failing update does not stop other updates or subsequent chunks.apps/web/app/(ee)/api/cron/partner-platforms/website/route.ts (1)
79-87: ⚡ Quick winUse explicit BigInt conversion for consistency.
On line 72, the code explicitly converts
domainRatingto BigInt for comparison:BigInt(domainRating). For consistency and clarity, the Prisma update should also use explicit conversion rather than relying on Prisma's implicit number-to-BigInt conversion.♻️ Proposed fix for explicit type conversion
await prisma.partnerPlatform.update({ where: { id: website.id, }, data: { - subscribers: domainRating, + subscribers: BigInt(domainRating), lastCheckedAt: new Date(), }, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/app/`(ee)/api/cron/partner-platforms/website/route.ts around lines 79 - 87, The Prisma update call in prisma.partnerPlatform.update is assigning subscribers: domainRating without explicit BigInt conversion; change that to use BigInt(domainRating) so the update uses an explicit BigInt (e.g., set subscribers to BigInt(domainRating)) when updating the record identified by website.id and keep lastCheckedAt: new Date() unchanged.apps/web/lib/partners/partner-platforms.ts (1)
38-46: ⚡ Quick winDuplication:
infoandstatcompute identical values for Website.Both the
infoarray entry and thestatfield use the same logic and produce the same string value. Consider extracting this computation to reduce duplication:♻️ Proposed refactor to eliminate duplication
+ const domainRatingStat = + website?.subscribers && website?.verifiedAt + ? `${Number(website.subscribers)} DR` + : null; info: [ - website?.subscribers && website?.verifiedAt - ? `${Number(website.subscribers)} DR` - : null, + domainRatingStat, ].filter(Boolean), - stat: - website?.subscribers && website?.verifiedAt - ? `${Number(website.subscribers)} DR` - : null, + stat: domainRatingStat,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/lib/partners/partner-platforms.ts` around lines 38 - 46, The info array entry and stat field duplicate the same conditional computation for Website (using website?.subscribers and website?.verifiedAt); extract that logic into a single const (e.g., computedSubscribersDR or getSubscribersDR) inside partner-platforms.ts and use that variable in both places (assign info: [computedSubscribersDR].filter(Boolean) and stat: computedSubscribersDR) so the conditional string creation is centralized and duplication is removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/app/`(ee)/api/cron/partner-platforms/website/get-domain-rating.ts:
- Around line 10-17: The fetch to Ahrefs in get-domain-rating.ts has no timeout;
wrap the request with an AbortController and a timer: create an AbortController,
pass controller.signal into the fetch call (the one that assigns to response),
start a setTimeout that calls controller.abort() after a configurable timeout
(e.g., 5–10s), and clear the timeout once the fetch completes; also ensure any
fetch error due to abort (AbortError) is handled/translated into a proper
timeout error path in the same function so the cron job can proceed gracefully.
- Around line 10-17: The fetch call that assigns to response when calling Ahrefs
(`fetch(...domain-rating-free...)`) doesn't handle non-2xx or 429 responses;
update the logic in the get-domain-rating handler to detect HTTP 429 and other
non-2xx statuses, implement a bounded retry with exponential backoff (honoring
the Retry-After header when present), and limit concurrent calls (or use a
centralized rate-limiter/throttle) to keep total requests under Ahrefs' ~60/min
dynamic threshold; ensure retries are capped (max attempts) and failures
propagate a clear error after exhaustion.
---
Duplicate comments:
In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/program/network/network-partner-card.tsx:
- Around line 261-263: remainingInvites can be undefined during load so change
the boolean logic to explicitly check for undefined: compute
atNetworkInviteLimit as remainingInvites !== undefined && remainingInvites === 0
(using the value from usePartnerNetworkInvitesUsage), and ensure disabled uses
that guarded atNetworkInviteLimit together with trialActive so we don't
erroneously allow invites while remainingInvites is still loading.
---
Nitpick comments:
In `@apps/web/app/`(ee)/api/cron/partner-platforms/website/route.ts:
- Around line 79-87: The Prisma update call in prisma.partnerPlatform.update is
assigning subscribers: domainRating without explicit BigInt conversion; change
that to use BigInt(domainRating) so the update uses an explicit BigInt (e.g.,
set subscribers to BigInt(domainRating)) when updating the record identified by
website.id and keep lastCheckedAt: new Date() unchanged.
In `@apps/web/app/`(ee)/api/cron/partner-platforms/youtube/route.ts:
- Around line 89-141: The batch uses Promise.all over updateChunk.map so a
single failed prisma.partnerPlatform.update will reject the whole chunk and
abort remaining work; modify the anonymous async map callback (the function
iterating updateChunks / processing channel and partnerPlatform) to catch errors
per-record—wrap the logic around the prisma.partnerPlatform.update (and any
awaiting work) in a try-catch (or switch to Promise.allSettled) and on error log
the channel/partnerPlatform id and continue so one failing update does not stop
other updates or subsequent chunks.
In `@apps/web/lib/partners/partner-platforms.ts`:
- Around line 38-46: The info array entry and stat field duplicate the same
conditional computation for Website (using website?.subscribers and
website?.verifiedAt); extract that logic into a single const (e.g.,
computedSubscribersDR or getSubscribersDR) inside partner-platforms.ts and use
that variable in both places (assign info:
[computedSubscribersDR].filter(Boolean) and stat: computedSubscribersDR) so the
conditional string creation is centralized and duplication is removed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7b40ec87-7983-4fca-801d-5104f8ead604
📒 Files selected for processing (8)
apps/web/app/(ee)/api/cron/partner-platforms/route.tsapps/web/app/(ee)/api/cron/partner-platforms/website/get-domain-rating.tsapps/web/app/(ee)/api/cron/partner-platforms/website/route.tsapps/web/app/(ee)/api/cron/partner-platforms/youtube/route.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/network/network-partner-card.tsxapps/web/lib/partners/partner-platforms.tsapps/web/ui/partners/partner-platforms-form.tsxapps/web/ui/partners/partner-star-button.tsx
Summary by CodeRabbit
New Features
Improvements
UI
Other